#!/usr/bin/env python3
"""Script for generating the match expression for `SlotGrid::determine_neighbor_associations()`.

The algorithm used to generate the expression should be readable, but it does not need to be
efficient, as it will only be run at build time.
"""
import itertools

from typing import List, Optional


def main():
    """Generate the match expression for `SlotGrid::determine_neighbor_associations()`"""
    # Create a list of all possible values that `array` may assume because the match expression
    # needs to be exhaustive.
    # Generate all possible combinations of boolean values for a 3x3 list
    variants = [list(variant) for variant in itertools.product((False, True), repeat=9)]
    # Reshape the variants into 3 · 3 lists
    arm_patterns = [[variant[i:i + 3] for i in range(0, 9, 3)] for variant in variants]

    print("match array {")
    # For each pattern, the neighbor associations are determined and both pattern and arm block are
    # printed.
    for pattern in arm_patterns:
        print_pattern = [" ".join("⬛" if slot else " " for slot in row) for row in pattern]
        print(f"""\
    // ╭───────╮
    // │ {print_pattern[0]} │
    // │ {print_pattern[1]} │
    // │ {print_pattern[2]} │
    // ╰───────╯
    {str(pattern).lower()} => vec![""")
        # Each neighbor association `HashSet` is defined with sorted Directions (please see
        # `enum Direction` for reference).
        # The `HashSet`s in each neighbor association vector are ordered by their first Direction,
        # which means that neighbor associations containing `North` are always first and their
        # definition starts with `North`; neighbor associations containing `East` but not `North`
        # are next, et cetera.

        # The following variables are used to signify whether the respective Direction is both
        # vacant and unprocessed, meaning that it has not been added to any neighbor association
        # yet.
        northern_neighbor_vacant_and_unprocessed = not pattern[0][1]
        eastern_neighbor_vacant_and_unprocessed = not pattern[1][2]
        southern_neighbor_vacant_and_unprocessed = not pattern[2][1]
        western_neighbor_vacant_and_unprocessed = not pattern[1][0]

        # Key for the match arm comments:
        # [space]: stands for the wildcard pattern `_` (https://peps.python.org/pep-0634/#id3)
        # ⬚:       vacant
        # ⬛:       occupied

        if northern_neighbor_vacant_and_unprocessed:
            print("""\
        HashSet::from([
            Direction::North,""")

            # East
            # ╭───────╮
            # │   ⬚ ⬚ │
            # │     ⬚ │
            # │       │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, False, False], [None, None, False], [None, None, None]],
            ):
                print("            Direction::East,")
                # east is added to `neighbor_associations` by this arm, so mark it as processed.
                eastern_neighbor_vacant_and_unprocessed = False
            # ╭───────╮
            # │ ⬚ ⬚   │
            # │ ⬚   ⬚ │
            # │ ⬚ ⬚ ⬚ │
            # ╰───────╯
            elif _match_pattern(
                pattern,
                [[False, False, None], [False, None, False], [False, False, False]],
            ):
                print("            Direction::East,")
                # east is added to `neighbor_associations` by this arm, so mark it as processed.
                eastern_neighbor_vacant_and_unprocessed = False

            # South
            # ╭───────╮
            # │   ⬚ ⬚ │
            # │     ⬚ │
            # │   ⬚ ⬚ │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, False, False], [None, None, False], [None, False, False]],
            ):
                print("            Direction::South,")
                # south is added to `neighbor_associations` by this arm, so mark it as
                # processed.
                southern_neighbor_vacant_and_unprocessed = False
            # ╭───────╮
            # │ ⬚ ⬚   │
            # │ ⬚     │
            # │ ⬚ ⬚   │
            # ╰───────╯
            elif _match_pattern(
                pattern,
                [[False, False, None], [False, None, None], [False, False, None]],
            ):
                print("            Direction::South,")
                # south is added to `neighbor_associations` by this arm, so mark it as
                # processed.
                southern_neighbor_vacant_and_unprocessed = False

            # West
            # ╭───────╮
            # │   ⬚ ⬚ │
            # │ ⬚   ⬚ │
            # │ ⬚ ⬚ ⬚ │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, False, False], [False, None, False], [False, False, False]],
            ):
                print("            Direction::West,")
                # west is added to `neighbor_associations` by this arm, so mark it as processed.
                western_neighbor_vacant_and_unprocessed = False
            # ╭───────╮
            # │ ⬚ ⬚   │
            # │ ⬚     │
            # │       │
            # ╰───────╯
            elif _match_pattern(
                pattern,
                [[False, False, None], [False, None, None], [None, None, None]],
            ):
                print("            Direction::West,")
                # west is added to `neighbor_associations` by this arm, so mark it as processed.
                western_neighbor_vacant_and_unprocessed = False

            print("        ]),")

        if eastern_neighbor_vacant_and_unprocessed:
            print("""\
        HashSet::from([
            Direction::East,""")

            # South
            # ╭───────╮
            # │       │
            # │     ⬚ │
            # │   ⬚ ⬚ │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, None, None], [None, None, False], [None, False, False]],
            ):
                print("            Direction::South,")
                # south is added to `neighbor_associations` by this arm, so mark it as
                # processed.
                southern_neighbor_vacant_and_unprocessed = False

            # West
            # ╭───────╮
            # │       │
            # │ ⬚   ⬚ │
            # │ ⬚ ⬚ ⬚ │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, None, None], [False, None, False], [False, False, False]],
            ):
                print("            Direction::West,")
                # west is added to `neighbor_associations` by this arm, so mark it as processed.
                western_neighbor_vacant_and_unprocessed = False

            print("        ]),")

        if southern_neighbor_vacant_and_unprocessed:
            print("""\
        HashSet::from([
            Direction::South,""")

            # West
            # ╭───────╮
            # │       │
            # │ ⬚     │
            # │ ⬚ ⬚   │
            # ╰───────╯
            if _match_pattern(
                pattern,
                [[None, None, None], [False, None, None], [False, False, None]],
            ):
                print("            Direction::West,")
                # west is added to `neighbor_associations` by this arm, so mark it as processed.
                western_neighbor_vacant_and_unprocessed = False

            print("        ]),")
        if western_neighbor_vacant_and_unprocessed:
            print("""\
        HashSet::from([
            Direction::West,
        ]),""")

        print("    ],")

    print("}")


def _match_pattern(pattern: List[List[bool]], case: List[List[Optional[bool]]]) -> bool:
    for pattern_element, case_element in zip(
        itertools.chain.from_iterable(pattern),
        itertools.chain.from_iterable(case),
    ):
        if case_element is not None and pattern_element != case_element:
            return False
    return True


if __name__ == '__main__':
    main()
